home *** CD-ROM | disk | FTP | other *** search
- #!/usr/local/bin/gawk -f
- # Use gawk for strftime()
- # @(#) lastlogin.gawk 3.3 97/04/20
- # 93/04/20 john h. dubois iii (john@armory.com)
- # 93/05/12 Deal with unstatable and nonexistant .lastlogin files.
- # 94/01/14 Added all options.
- # 94/02/08 Added u option and check for bogus dates; use mod time instead of
- # access time on .lastlogin; use name of user running program if
- # no names given.
- # 94/02/09 Added l option, and explicit exit 0 from stat command to avoid
- # gawk error message.
- # 95/01/21 Added o options.
- # 95/03/05 Improved help description of -o.
- # 95/05/13 Added x option.
- # 95/05/25 Don't print empty info lines for users who have never logged in.
- # 95/07/31 Deal with new stat error messages
- # 95/08/10 Fixed s option so it works correctly.
- # 95/09/09 Added rt options.
- # 95/12/30 Added H option, and use of rcfile
- # 96/01/17 Added .zhistory as history file for zsh
- # 96/01/20 Also use rcfile in UHOME.
- # 96/01/26 Do not print header if no reports will be printed.
- # 96/05/24 Added n option.
- # 96/06/03 Added Ac options.
- # 96/06/24 Added N option.
- # 96/06/29 Added gCe options.
- # 96/09/02 Print estimated account creation time for never-logged-in accounts.
- # 97/02/01 Use PWSearch(); added options for its facilities
- # 97/02/17 Added popmail check.
- # 97/04/16 Added LdP options.
- # 97/04/20 Added SEz options.
-
- # Globals Attempt, Suc, and Logout are each either left null or set to a
- # sequence number in [1..3]
- BEGIN {
- Name = "lastlogin"
- NewFormat = "%a %b %d %H:%M"
- OldFormat = "%a %b %d %Y"
- NewShortFormat = "%b %d %H:%M"
- OldShortFormat = "%b %d %Y"
- rcFile = ".lastloginrc"
- Usage = "Usage:\n" \
- Name " [-aAcCeEgGhHilMnNoOpRsTLz] [-r<dir>] [-t<time-format>] [user ...]"
- # f is pseudo-opt for REPORTS var.
- # Unused: bBDIjJkKOqQTuUVwXyYZ
- ARGC = Opts(Name,Usage,"cCdef:GHnPsSt:aAEghilLMNopr:Rxz",0,
- "~/" rcFile ":$UHOME/" rcFile, "CASE,AUTOCASE,DATE,EXACT,REPORTS,"\
- "AUTOGCOS,HEADER,NAME,PERIOD,SORT,NONEVER,TIMEFORMAT",
- 0,"z",0,"","laop,M,N;i,A,L")
- if ("h" in Options) {
- printf \
- "%s: show last login times of users.\n"\
- "%s\n"\
- "If no user names are given, the invoking user's times are reported.\n"\
- "Options:\n"\
- "Some of the following options can also be set by assigning values to\n"\
- "variables in a configuration file named %s, which is searched for in\n"\
- "the invoking user's home directory and in the directory specified by the\n"\
- "environment variable UHOME, if it is set (if both files exist, values set\n"\
- "in the former take precedence). Variables are assigned to with the\n"\
- "syntax: varname=value or in the case of flags, by simply putting the\n"\
- "indicated variable name in the file without a value.\n"\
- "Report type selection options:\n"\
- "-l: Report last successful login (the default).\n"\
- "-a: Report time of last login attempt, whether successful or not.\n"\
- "-o: Report time of last logout. This option only works for users whose\n"\
- " login shell maintains a command history between logins. A - will be\n"\
- " printed instead of a logout time for users of other shells, or if the\n"\
- " user's history file is not being maintained. The -o report will give\n"\
- " an incorrect result for currently logged in users of shells that\n"\
- " continuously write to their history files.\n"\
- "-p: Report last popmail access. This depends on the POP daemon touching\n"\
- " a file named .<username>.pop in /usr/spool/mail whenever a popmail\n"\
- " access is done.\n"\
- " -l, -a, -o, and -p can be used together to get multiple reports.\n"\
- " The order of times printed on each line will be the same as the order\n"\
- " the options are given in.\n"\
- "If all four reports are requested, an abbreviated output format is used.\n"\
- "The [laop] options may be set in the rcfile by assigning them to the\n"\
- "REPORTS variable, e.g.: REPORTS=lo\n"\
- "-M: Consistency check: Among the selected users, report (only) those who\n"\
- " have a logout file of the type described above, but do not have a\n"\
- " .lastlogin file. Use with -A to find all such users.\n"\
- "-N: Print only the names of users who have never logged in, along with an\n"\
- " estimation of the dates that the accounts were created, determined by\n"\
- " checking the creation times of the accounts' shell startup files.\n"\
- "General options:\n"\
- "-h: Print this help.\n"\
- "-H: Print a header. Variable: HEADER\n"\
- "-r<dir>: Specify an alternate root filesystem. The /etc/passwd file and\n"\
- " home directories are searched for relative to <dir>.\n"\
- "-z: Do not read configuration file.\n"\
- "Output formatting options:\n"\
- "-s: Sort output by the first time printed for each line. Variable: SORT\n"\
- "-n: Include each user's \"real name\" in the output. Variable: NAME\n"\
- "-E: Print only the most recent time among the reports requested.\n"\
- "-t<time-format>: Use strftime(S)-style time format string <time-format>\n"\
- " to format all times. If an empty string is passed, the epoch time (in\n"\
- " seconds) is used. The default is to use '%s' for times up to\n"\
- " half a year ago, and '%s' for times before that. If all four\n"\
- " reports are requested and the COLUMNS environment variable is not set\n"\
- " to a value >= 97, the day of the week is omitted. Variable: TIMEFORMAT\n"\
- "-d: Show each time as a date and time (this is the default). (DATE)\n"\
- "-P: Show each time as an \"age\" - the period that has elapsed between the\n"\
- " time and the current time. Ages are printed as: [<days>d] hh:mm\n"\
- " (PERIOD) Both -d and -P may be given; two lines will be printed.\n"\
- "-S: Do not print anything for users who have never logged in. (NONEVER)\n"\
- "User selection options:\n"\
- "-A: Report on all \"real\" users (those who have a shell in /etc/shells).\n"\
- "-L: Report on all users currently logged in.\n"\
- "-i: Read names of users to report on from the standard input.\n"\
- " Multiple lines of input and multiple names per line may be given.\n"\
- "User name matching options:\n"\
- "-g: Search for names in the GCOS (\"real name\") field instead of the\n"\
- " account name field. The search is not case sensitive. Unlike\n"\
- " searches by account name, the name need not match the entire field.\n"\
- " The words of each name given must occur in the same order in a GCOS\n"\
- " field in order for a match to occur. Multiple password file entries\n"\
- " may be matched by one name. If multiple words of a name are given,\n"\
- " they must be quoted to form a single argument. Example:\n"\
- " %s -g 'john somebody'\n"\
- "-R: Treat the given names as unanchored regular expressions. Names are\n"\
- " compared to the same field that would be searched without -R. With\n"\
- " this option, the meaning of -e is changed to mean that the regular\n"\
- " expression should be anchored at the start and end, and the meaning\n"\
- " of -c is changed to mean that the match should be case sensitive.\n"\
- "-G: If a name is not found as a user name, search for it in the GCOS field\n"\
- " as though -g had been given. (AUTOGCOS)\n"\
- "-e: An exact match of the full GCOS field is required. (EXACT)\n"\
- "-c: Matches against the GCOS field are made case sensitive. (CASE)\n"\
- "-C: Like -c, except that a match is only case sensitive for names that\n"\
- " are given with some characters in upper case. (AUTOCASE)\n"\
- "If -e, -c, or -C is given on the command line and -R is not given, the -g\n"\
- "option is turned on. If the e, c, or C option is turn on by puttings its\n"\
- "variable in a config file, they only have an effect if -R or -g is given.\n",
- Name,Usage,rcFile,Name,NewFormat,OldFormat
- exit 0
- }
-
- autoGCOS = "G" in Options
- CaseSensitive = "c" in Options
- Full = "e" in Options
- regex = "R" in Options
- autoCase = "C" in Options
- GCOS = ("g" in Options) || !regex && (CmdLineOpt(Options,"e") || \
- CmdLineOpt(Options,"C") || CmdLineOpt(Options,"c"))
-
- mostRecent = "E" in Options
- Consistency = "M" in Options
- AllUsers = "A" in Options
- LoggedInUsers = "L" in Options
- ReadInput = "i" in Options
- Header = "H" in Options
- Sort = "s" in Options
- Debug = "x" in Options
- neverOnly = "N" in Options
- printNever = !("S" in Options)
- PrintPeriod = "P" in Options
- PrintDate = !PrintPeriod || ("d" in Options)
- if ("r" in Options) {
- ROOT = Options["r"]
- ReadPasswd(ROOT "/etc/passwd")
- }
- else
- ReadPasswd() # set PW_ names.
- Field = GCOS ? PW_GCOS : PW_NAME
-
- # Get sequence number for each option
- # All of this is to assign Attempt, Suc, and Logout either nothing (if
- # their option was not given), or a number 1-4 indicating what order
- # they were given in. The problem is that there will be gaps in their
- # sequence numbers if other options are given with them.
- # Set array values for use by H option.
- InitArr(QueryOpts,"a:l:o:p:r",
- "Last Attempt:Last Login:Last Logout:POP Access:Most Recent",":")
- if (neverOnly)
- ;
- else if (!("a" in Options || "l" in Options || "o" in Options || \
- "p" in Options) && "f" in Options) {
- NumQuery = SplitS(Options["f"],Query)
- for (Q = 1; Q <= NumQuery; Q++) {
- if (Debug)
- printf "Processing option %s of REPORTS string: %s\n",Q,
- Query[Q] > "/dev/stderr"
- if (!(Query[Q] in QueryOpts)) {
- printf "Unknown query type '%s' given with REPORTS var.\n",
- Q > "/dev/stderr"
- exit 1
- }
- Query[Query[Q]] = Q
- }
- }
- else {
- # For each query option letter...
- for (Q in QueryOpts)
- # If the option was given on the command line...
- if ((Q,"num",1) in Options)
- # Set Query[option-letter] to its relative pos on cmd line
- Query[Options[Q,"num",1]] = Q
- # Sort the options to get a map that puts them in their cmd line order
- NumQuery = qsortByArbIndex(Query,k)
- # Set Query[option-letter] to an integer giving its pos, 1...numqueries
- for (i = 1; i <= NumQuery; i++) {
- Query[Query[k[i]]] = i
- Query[i] = Query[k[i]] # for H option
- }
- }
- Attempt = Query["a"]
- Suc = Query["l"]
- Logout = Query["o"]
- POPmail = Query["p"]
-
- Abbreviate = \
- NumQuery == 4 && (!("COLUMNS" in ENVIRON) || ENVIRON["COLUMNS"] < 97)
- if ("t" in Options)
- NewFormat = OldFormat = Options["t"]
- else if (Abbreviate) {
- NewFormat = "%b %d %H:%M"
- OldFormat = "%b %d %Y"
- }
- if (Debug)
- printf "Attempt=%s Suc=%s Logout=%s POPmail=%s\n",
- Attempt,Suc,Logout,POPmail > "/dev/stderr"
- if (Consistency) {
- Logout = 1
- Suc = 2
- NumQuery = 2
- Query[1] = "o"
- Query[2] = "l"
- }
- else if (!(Attempt || Logout || Suc || POPmail)) {
- Suc = 1
- NumQuery = 1
- Query[1] = "l"
- }
-
- # Get user names to search for
- if (!(ReadInput || AllUsers || LoggedInUsers) && ARGC < 2) {
- ARGC = 2
- ARGV[1] = id()
- }
- if (ARGC > 1)
- NumUsers = FindLastLogins(ARGC,ARGV,Attempt,Suc,Logout,POPmail,
- UserTimes,Users,0,Field,autoGCOS,CaseSensitive,autoCase,
- regex,Full)
- else if (ReadInput) {
- while ((ret = (getline < "/dev/stdin")) == 1) {
- for (i = 1; i <= NF; i++)
- ARGV[i] = $i
- NumUsers = \
- FindLastLogins(i,ARGV,Attempt,Suc,Logout,POPmail,UserTimes,Users,0,
- Field,autoGCOS,CaseSensitive,autoCase,regex,Full)
- }
- close("/dev/stdin")
- if (ret)
- print "Error reading stdin" > "/dev/stderr"
- }
- else if (AllUsers || LoggedInUsers) {
- if (AllUsers)
- while (getpwent(PWEnt,-1) == 1)
- RealUsers[++RUserCt] = PWEnt[PW_NAME]
- else {
- cmd = "who -q -n1"
- while ((cmd | getline) == 1)
- if ($1 != "#" && !($1 in gotUsers)) {
- gotUsers[$1]
- RealUsers[++RUserCt] = $1
- }
- close(cmd)
- }
- NumUsers = \
- FindLastLogins(RUserCt,RealUsers,Attempt,Suc,Logout,POPmail,UserTimes,
- Users,1,Field,autoGCOS,CaseSensitive,autoCase,regex,Full)
- }
- PrintLines(UserTimes,Sort,Users,NumQuery,NumUsers,Header,Query,
- QueryOpts,"n" in Options,Consistency,Abbreviate ? 14 : 16,NeverOnly,Suc,
- PrintDate,PrintPeriod,mostRecent,printNever)
- }
-
- # PWSearch: find entries in password file that match given criteria.
- # pat: Fixed string or pattern to compare to a password field.
- # Field: which password field to compare pat to, from the PW_* set.
- # Entries[]: The indexes of matching entries in the password database are
- # returned as values in Entries[]. The index for each value in an
- # integer giving the order in which it was found (starting with 1).
- # autoGCOS: If Field is PW_NAME and autoGCOS is true, pat is first searched
- # for in the name field; if it is not found there, it is searched for in
- # the "real name" field (part of the GCOS field).
- # ignoreCase: make matches be independent of case.
- # autoCase: If ignoreCase is false and autoCase is true, then matches are case
- # case independent only if there are no upper case letters in pat.
- # regex: pat is treated as a regular expression and compared to the "real name"
- # field.
- # Full: If regex is true, it is required to match the entire "real name" field.
- # Globals: Debug, pwFieldNames, PW_*
- # Return value: number of matching entries found (the highest index in
- # Entries[]).
- function PWSearch(pat,Field,Entries,autoGCOS,ignoreCase,autoCase,regex,Full,
- oIC,PWEnt,num) {
- if (ignoreCase && autoCase)
- ignoreCase = (pat !~ /[A-Z]/)
- num = 0
- if (Field == PW_NAME && autoGCOS)
- if (num = PWSearch(pat,Field,Entries,0,ignoreCase,0,regex,Full))
- return num
- else {
- Field = PW_GCOS
- if (Debug)
- print "No match on name field, trying GCOS..." > "/dev/stderr"
- }
- if (Debug)
- printf \
- "Searching for %s in field %d (\"%s\") with case sensitivity %s,\n"\
- "exact match %s\n",pat,Field,pwFieldNames[Field],
- ignoreCase ? "off" : "on",Full ? "on" : "off"
- if (regex) {
- oIC = IGNORECASE
- IGNORECASE = ignoreCase
- if (Full)
- pat = "^(" pat ")$"
- setpwent()
- if (Debug)
- print "Pattern: " pat > "/dev/stderr"
- while (getpwent(PWEnt,0,PW_REAL) != ":")
- if (PWEnt[Field] ~ pat)
- Entries[++num] = PWEnt[PW_RECORD]
- IGNORECASE = oIC
- }
- else if (getpw(Field,pat,PWEnt,"",0,ignoreCase,Full)) {
- Entries[++num] = PWEnt[PW_RECORD]
- while (getpw(Field,pat,PWEnt,"",1,ignoreCase,Full))
- Entries[++num] = PWEnt[PW_RECORD]
- }
- if (Debug)
- printf "Got %d matches.\n",num > "/dev/stderr"
- return num
- }
-
- # PrintLines: print login/logout time report.
- # UserTimes[] contains the user login/logout times, indexed by User,QueryNum
- # where User is the user name and QueryNum is an integer from 1 through n,
- # where n is the number of times to be reported for each user. Times will
- # be listed in order of index.
- # Sort is true if the output should be sorted by the first time to be reported
- # for each user.
- # If Consistency is true, only users who have file 1 and not file 2 are
- # reported on.
- # Users[] contains the names of users to be reported on, indexed starting from
- # 1. Users will be reported on in order of their indexes.
- # NumQuery is the number of times to be reported for each user.
- # NumUsers is the highest index that may be used in Users[].
- # Header is true if a header should be printed.
- # Query[] contains the numbers of the queries being done, indexed 1..n
- # QueryNames[] contains the names of the queries, index by query number.
- function PrintLines(UserTimes,Sort,Users,NumQuery,NumUsers,Header,Query,
- QueryNames,RealName,Consistency,UserWidth,NeverOnly,Suc,PrintDate,PrintPeriod,
- mostRecent,printNever,
- i,User,Num,k,Key,UserFormat,TimeLength,TimeFormat,Name,CurTime,Time) {
- # If successful-login was included in reports requested, and no data of any
- # type was found for user, complain & do not include in main report.
- if (!Consistency && Suc)
- for (i = 1; i <= NumUsers; i++) {
- if (!(i in Users))
- continue
- User = Users[i]
- for (Num = 1; Num <= NumQuery; Num++)
- if ((User,Num) in UserTimes)
- break
- if (Num > NumQuery) { # No reports for this user
- if (printNever)
- NeverLoggedIn(User,NeverOnly,"/dev/stdout")
- delete Users[i]
- }
- }
- if (NeverOnly)
- return
- if (mostRecent) {
- if (NumQuery > 1) # Put most recent time in slot 1
- for (i = 1; i <= NumUsers; i++) {
- if (!(i in Users))
- continue
- User = Users[i]
- Time = UserTimes[User,1]
- for (Num = 2; Num <= NumQuery; Num++)
- if (UserTimes[User,Num] > Time)
- Time = UserTimes[User,Num]
- UserTimes[User,1] = Time
- }
- NumQuery = 1
- Query[1] = "r"
- }
- if (Sort) {
- for (i in Users) {
- User = Users[i]
- if ((User,1) in UserTimes)
- Key[User] = UserTimes[User,1]
- else
- Key[User] = 0
- }
- NumUsers = qsortArbIndByValue(Key,k)
- for (i in k)
- Users[i] = k[i]
- }
- if (Debug)
- printf "%d report(s); %d user(s) found in Users[]\n",
- NumQuery,NumUsers > "/dev/stderr"
- UserFormat = "%-8s"
- if (RealName)
- UserFormat = UserFormat " %-" UserWidth "." UserWidth "s"
- CurTime = systime() # Do this once for consistency
- # Make TimeLength be the maximum of the data names and the formatted data
- # lengths.
- for (Num = 1; Num <= NumQuery; Num++)
- TimeLength = max(TimeLength,length(QueryNames[Query[Num]]))
- # Get length of formatted data for epoch time 0 (maximum age)
- TimeLength = max(TimeLength,length(FmtTime(0,0,PrintDate)))
- if (PrintDate && PrintPeriod)
- TimeLength = max(TimeLength,length(FmtTime(0,0,0)))
-
- TimeFormat = " %-" TimeLength "s"
- if (Header) {
- # Before printing header, make sure there will be at least one report
- for (i = 1; i <= NumUsers && !(i in Users); i++)
- ;
- if (i > NumUsers)
- return
- printf UserFormat,"User","Name"
- for (Num = 1; Num <= NumQuery; Num++)
- printf TimeFormat,QueryNames[Query[Num]]
- print ""
- }
- for (i = 1; i <= NumUsers; i++) {
- if (!(i in Users))
- continue
- User = Users[i]
- # If doing a consistency check, logout time must be found and login
- # time must not be found.
- if (Consistency && (!((User,1) in UserTimes) || (User,2) in UserTimes))
- continue
- Name = getpwnam(User,PWEnt,PW_REAL)
- if (length(Name) > UserWidth)
- Name = substr(Name,1,UserWidth-1) ">"
- printf UserFormat,User,Name
- # If no data available for this user for a data type,
- # or if the logout time has been requested and the logout time for the
- # user is earlier than the login time, print a '-'
- # Oops... that doesn't make sense, since a csh user's logout time will
- # be earlier than login time when the user is logged in.
- # && (Num != Logout || !((User,Suc) in UserTimes) || \
- # UserTimes[User,Logout] >= UserTimes[User,Suc]) )
-
- # At least one of PrintDate and Print Period will be true.
- for (Num = 1; Num <= NumQuery; Num++)
- printf TimeFormat,((User,Num) in UserTimes) \
- ? FmtTime(UserTimes[User,Num],CurTime,PrintDate) : "-"
- print ""
- if (PrintDate && PrintPeriod) {
- printf UserFormat,"",""
- for (Num = 1; Num <= NumQuery; Num++)
- printf TimeFormat,((User,Num) in UserTimes) \
- ? FmtTime(UserTimes[User,Num],CurTime,0) : "-"
- print ""
- }
- }
- }
-
- function lookupNames(argc,argv,IgnoreCase,ExactMatch, PWEnt,i,outInd) {
- CopyArr(argv,givenNames)
- split("",argv)
- outInd = 1
- ReadPasswd()
- for (i = 1; i < argc; i++) {
- name = givenNames[i]
- if ((user = getpwreal(name,PWEnt,PW_NAME,0,IgnoreCase,ExactMatch)) \
- == ":")
- printf "%s: %s: No user with this real name.\n",
- Name,name > "/dev/stderr"
- else {
- argv[outInd++] = user
- while ((user = \
- getpwreal("",PWEnt,PW_NAME,1,IgnoreCase,ExactMatch)) != ":")
- argv[outInd++] = user
- }
- }
- return outInd
- }
-
- # Input vars:
- # ARGC & ARGV[]: names of users to check.
- # Unsuc: Report last login attempt
- # Suc: Report last successful login
- # POPmail: Report last popmail access
- # Logout: Report last logout time
- # If nonzero, Unsuc, Suc, and Logout also give the index to store the
- # associated time under in UserTimes[].
- # If Consistency is true, no complaints are issued about users who have
- # no .lastlogin file, and they are included in Users[].
- # If Consistency is not true, users who have no .lastlogin file are not
- # included in Users[].
- # Output vars:
- # UserTimes[]: Login/logout times for users, indexed by UserName,TimeType
- # where TimeType is in integer from 1-3 which indicates what order that
- # time should be printed in relative to the others.
- # Users[]: Which users from ARGV[] actually existed, indexed by the order
- # they appeared in ARGV.
- # Globals: ONum ROOT
- function FindLastLogins(ARGC,ARGV,Unsuc,Suc,Logout,POPmail,UserTimes,Users,
- Consistency,Field,autoGCOS,CaseSensitive,autoCase,regex,Full,
- i,User,PWEnt,Home,Files,Cmd,Access,Modify,Create,Line,UserFiles,Shell2Hist,
- Shell,Elem,UsersInd,luser,Num,Entries,u,isPOP) {
- # Note: the history file can be changed in any of these shells.
- # zsh maintains history but has no default history file; try .zhistory
- InitArr(Shell2Hist,"csh,tcsh,ksh,bash,zsh",
- ".history,.history,.sh_history,.bash_history,.zhistory",",")
- if (gcos)
- ARGC = lookupNames(ARGC,ARGV,IgnoreCase,ExactMatch)
- for (i = 1; i < ARGC; i++) {
- User = ARGV[i]
- Num = \
- PWSearch(User,Field,Entries,autoGCOS,!CaseSensitive,autoCase,regex,Full)
- if (Num)
- for (u = 1; u <= Num; u++) {
- PWGetFields(PWLines[Entries[u]],PWEnt,PW_REAL)
- Users[++ONum] = User = PWEnt[PW_NAME]
- UsersInd[User] = ONum
- Home = ROOT PWEnt[PW_HOME]
- Home2User[Home] = User
- if (Unsuc || Suc)
- Files = Files " " Home "/.lastlogin"
- if (Logout) {
- Shell = PWEnt[PW_SHELL]
- sub(".*/","",Shell)
- if (Shell in Shell2Hist)
- Files = Files " " Home "/" Shell2Hist[Shell]
- }
- if (POPmail)
- Files = Files " /usr/spool/mail/." User ".pop"
- }
- else
- printf "%s: %s: No such user.\n",Name,User > "/dev/stderr"
- }
- if (Files == "")
- return ONum
- Cmd = "exec stat '-c ' -nfnamc " Files " 2>&1"
- if (Debug)
- print "stat command: " Cmd > "/dev/stderr"
- # Lines from stat will be in this form:
- # name access modify create
- # .lastlogin 790501360 790501362 790501362
- while ((Cmd | getline Line) == 1) {
- if (Debug)
- print "stat returned: " Line > "/dev/stderr"
- sub("^stat: ","",Line) # Error message from stat
-
- if (!split(Line,Elem," ")) {
- if (Debug)
- printf "Skipping empty stat line\n" > "/dev/stderr"
- continue
- }
- Home = StatFile = Elem[1]
- Access = Elem[2]
- Modify = Elem[3]
- Create = Elem[4]
- sub("^.*/","",StatFile) # Get rid of directory components
- # In error messages, stat puts : after filename; get rid of it.
- sub(":$","",StatFile)
- # Get rid of trailing component of filename, leaving home directory
- sub("/[^/]*$","",Home)
- if (isPOP = \
- (POPmail && Home == "/usr/spool/mail" && StatFile ~ "^\\..*\\.pop$")) {
- User = substr(StatFile,2,length(StatFile)-5)
- if (Debug)
- printf "Got popmail file for: <%s>\n",User > "/dev/stderr"
- }
- else {
- if (!(Home in Home2User)) {
- printf "Oops... got unknown home '%s' from stat output:\n%s\n",
- Home,Line > "/dev/stderr"
- continue
- }
- User = Home2User[Home]
- }
- # If user isn't in Users[], we already determined that this user
- # has never logged in, so skip any other data for it.
- if (!(User in UsersInd)) {
- if (Debug)
- printf "Ignoring further data for %s\n",User > "/dev/stderr"
- continue
- }
- if (Access !~ "^[0-9]+$") {
- if (Line !~ "No such file") {
- sub("^[^ ]* ","")
- printf "Cannot stat %s's %s file: %s\n",User,StatFile,Line \
- > "/dev/stderr"
- }
- continue
- }
- if (Debug)
- printf \
- "user: %s suc: %d unsuc: %d statfile: %s modify: %d create: %d\n",
- User,Suc,Unsuc,StatFile,Modify,Create > "/dev/stderr"
- if (StatFile == ".lastlogin") {
- if (Suc)
- UserTimes[User,Suc] = Modify
- if (Unsuc)
- UserTimes[User,Unsuc] = Create
- }
- else if (isPOP)
- UserTimes[User,POPmail] = Modify
- else if (Logout)
- UserTimes[User,Logout] = Modify
- }
- close(Cmd)
- return ONum
- }
-
- function NeverLoggedIn(User,NeverOnly,file, Home) {
- Home = getpwnam(User,PWEnt,PW_HOME)
- CmdReadLine("stat -nt%y/%m/%d -fC " Home "/.*profile " \
- Home "/.login 2>/dev/null",0)
- if (neverOnly)
- printf "%8s %8s %s\n",$1,User,getpwnam(User,PWEnt,PW_REAL)
- else {
- printf "%s (%s): Never logged in",
- User,getpwnam(User,PWEnt,PW_REAL) > file
- if ($1 ~ "^[0-9][0-9]/[0-1][0-9]/[0-3][0-9]$")
- print " (account created on " $1 ")." > file
- else
- print "." > file
- }
- }
-
- function FmtTime(Time,CurTime,PrintDate, Age,ret) {
- if (PrintDate) {
- # If older than a half year ago
- if (Time < (CurTime - 182 * 24 * 3600))
- return OldFormat ? strftime(OldFormat,Time) : Time
- else
- return NewFormat ? strftime(NewFormat,Time) : Time
- }
- else {
- Age = CurTime - Time
- if (Age < 0) { # should not happen
- ret = "-"
- Age = -Age
- }
- if (Age >= 86400) {
- ret = ret int(Age/86400) "d "
- Age = Age % 86400
- }
- Age = int(Age / 60)
- ret = sprintf("%s%d:%02d",ret,Age/60,Age%60)
- return ret
- }
- }
-
- # MakeSet: make a set from a list.
- # An index with the name of each element of the list
- # is created in the given array.
- # Input variables:
- # Elements is a string containing the list of elements.
- # Sep is the character that separates the elements of the list.
- # Output variables:
- # Set is the array.
- # Return value: the number of elements added to the set.
- function MakeSet(Set,Elements,Sep, i,Num,Names) {
- Num = split(Elements,Names,Sep)
- for (i = 1; i <= Num; i++)
- Set[Names[i]]
- return Num
- }
-
- # Returns the number of elements in set Set
- function NumElem(Set, elem,Num) {
- for (elem in Set)
- Num++
- return Num
- }
-
- # id returns the login name of the user who owns the current process
- function id( Cmd,line,elem) {
- Cmd = "exec /usr/bin/id"
- Cmd | getline line
- split(line,elem,"[()]")
- close(Cmd)
- return elem[2]
- }
-
- # Arr is an array of values with arbitrary indices.
- # Array k is returned with numeric indices 1..n.
- # The values in k are the indices of array arr,
- # ordered so that if array arr is stepped through
- # in the order arr[k[1]] .. arr[k[n]], it will be stepped
- # through in order of the values of its elements.
- # The return value is the number of elements in the array (n).
- function qsort_arb_ind(arr,k, ArrInd,ElNum) {
- ElNum = 0
- for (ArrInd in arr)
- k[++ElNum] = ArrInd
- qsortseg(arr,k,1,ElNum)
- return ElNum
- }
-
- # @(#) qsortseg.awk 2.0 94/01/20
- # Non-recursive qsort. Slightly slower than recursive qsort,
- # but gawk 2.15.3 chokes on recursive version with internal error.
- # Sort a segment of an array.
- # Arr[] contains data with arbitrary indices.
- # k[] has indices 1..nelem, with the indices of Arr[] as values.
- # This function sorts the elements of Arr that are pointed to by
- # k[start..end], swapping the values of elements of k[] so that
- # when this function returns Arr[k[start..end]] will be in order.
- function qsortseg(arr,k,start,end,
- left,right,sepval,tmp,tmpe,tmps,ind,val,S,E,stackptr) {
- stackptr = 0
- S[0] = start
- E[0] = end
- while (stackptr >= 0) {
- # pop values from stack
- left = start = S[stackptr]
- right = end = E[stackptr]
- stackptr--
- # handle two-element case explicitly for a tiny speedup
- if ((end - start) == 1) {
- if (arr[tmps = k[start]] > arr[tmpe = k[end]]) {
- k[start] = tmpe
- k[end] = tmps
- }
- continue
- }
- sepval = arr[k[int((left + right) / 2)]]
- # Make every element <= sepval be to the left of every element > sepval
- while (left < right) {
- while (arr[k[left]] < sepval)
- left++
- while (arr[k[right]] > sepval)
- right--
- if (left < right) {
- tmp = k[left]
- k[left++] = k[right]
- k[right--] = tmp
- }
- }
- if (left == right)
- if (arr[k[left]] < sepval)
- left++
- else
- right--
- if (start < right) {
- S[++stackptr] = start
- E[stackptr] = right
- }
- if (left < end) {
- S[++stackptr] = left
- E[stackptr] = end
- }
- }
- }
-
- # Arr is an array of values with arbitrary indices.
- # Array k is returned with numeric indices 1..n.
- # The values in k are the indices of array arr,
- # ordered so that if array arr is stepped through
- # in the order arr[k[1]] .. arr[k[n]], it will be stepped
- # through in order of the values of its indices.
- # The return value is the number of elements in the array (n).
- function qsort_by_index(arr,k, ArrInd,end) {
- end = 0
- for (ArrInd in arr)
- k[++end] = ArrInd
- qsort_num_ind(k,1,end)
- return end
- }
-
- # Indexes do not preserve numeric type, so must be forced
- function qsort_by_num_index(arr,k, ArrInd,end) {
- end = 0
- for (ArrInd in arr)
- k[++end] = ArrInd+0
- qsort_num_ind(k,1,end)
- return end
- }
-
- # qsort_num_ind: sort an array
- # arr is the array to be sorted.
- # It must have contiguous numeric indexes.
- # start and end are the starting and ending indexes of the range to be sorted.
- function qsort_num_ind(arr,start,end, left,right,sepval,tmp,tmpe,tmps) {
- # handle two-element case explicitly for a tiny speedup
- if ((start - end) == 1) {
- if ((tmps = arr[start]) > (tmpe = arr[end])) {
- arr[start] = tmpe
- arr[end] = tmps
- }
- return
- }
- left = start+0
- right = end+0
- sepval = arr[int((left + right) / 2)]
- while (left < right) {
- while (arr[left] < sepval)
- left++
- while (arr[right] > sepval)
- right--
- if (left <= right) {
- tmp = arr[left]
- arr[left++] = arr[right]
- arr[right--] = tmp
- }
- }
- if (start < right)
- qsort_num_ind(arr,start,right)
- if (left < end)
- qsort_num_ind(arr,left,end)
- }
-
- function abort(s) {
- print s
- exit(1)
- }
-
- ### Begin Strings routines
-
- # Delete the string starting at Start and having length Num from the middle
- # of string S, and return the remaining part.
- function DelStr(S,Start,Num) {
- return substr(S,1,Start - 1) substr(S,Start+Num)
- }
-
- # Insert NewStr into S at position Pos (between the Pos-1 and the Pos
- # characters). S is padded with spaces if neccessary.
- function InsertStr(S,Pos,NewStr, e) {
- e = length(S)+1 # The position after the end of S
- if (e >= Pos)
- return substr(S,1,Pos-1) NewStr substr(S,Pos)
- for (; e < Pos; e++)
- S = S " "
- return S NewStr
- }
-
- # Search for char C in string S starting at position Pos, in the direction
- # specified by Dir (1 = forward, -1 = backward).
- # Return position char found at for success, 0 if not found before start or end
- # of string.
- function FindC(S,Pos,C,Dir, FoundC) {
- while (Pos > 0 && (FoundC = substr(S,Pos,1)) != C && FoundC != "")
- Pos += Dir
- if (FoundC == C)
- return Pos
- else
- return 0
- }
-
- # Split string S into array Arr, one character per index, starting with 1.
- # The number of characters in the string is returned.
- function SplitS(S,Arr, len,i) {
- len = length(S)
- for (i = 1; i <= len; i++)
- Arr[i] = substr(S,i,1)
- return len
- }
-
- # Paste NewStr onto S at position Pos, overwriting what was there
- # S is padded with spaces if neccessary.
- function PasteStr(S,Pos,NewStr, e) {
- e = length(S)+1 # The position after the end of S
- if (e >= Pos)
- return substr(S,1,Pos-1) NewStr substr(S,Pos+length(NewStr))
- for (; e < Pos; e++)
- S = S " "
- return S NewStr
- }
-
- ### End Strings routines
-
- # Put a list of login shells (from /etc/shells) into set LoginShells[].
- # Returns -1 if /etc/shells could not be read, else the number of shells found.
- function ReadShells(LoginShells, ret,Num,Line) {
- while (ret = ((getline Line < "/etc/shells") == 1))
- if (Line ~ "^/") {
- Num++
- sub(/[ \t]+/,"",Line)
- LoginShells[Line]
- }
- close("/etc/shells")
- _DidReadShells = 1
- return ret ? -1 : Num
- }
-
- ### Begin array routines
-
- # InitArr: Initialize an array with values.
- # Ind and Vals are separated into lists on Sep.
- # For each item in Ind, an index with that name is created in Arr[],
- # and the value with the same position in Vals is stored in it.
- # Global variables: none.
- function InitArr(Arr,Ind,Vals,sep, numind,indnames,values) {
- split(Ind,indnames,sep)
- split(Vals,values,sep)
- for (numind in indnames)
- Arr[indnames[numind]] = values[numind]
- }
-
- function ClearArr(Arr, Elem) {
- for (Elem in Arr)
- delete Arr[Elem]
- }
-
- function CopyArr(From,To, Elem) {
- for (Elem in From)
- To[Elem] = From[Elem]
- }
-
- # Subtract the values in Subtrahend from those in Minuend
- function SubtractArr(Minuend,Subtrahend, Elem) {
- for (Elem in Subtrahend)
- Minuend[Elem] -= Subtrahend[Elem]
- }
- # For each element of the array In, an element is created in Out having
- # an index equal to the value of the element in In and a value equal to
- # the index of the element in In.
- function Invert(In,Out, Index) {
- for (Index in In)
- Out[In[Index]] = Index
- }
-
- # Assign: make an array from a list of assignments.
- # An index with the name of each variable in the list is created in the array.
- # Its value is set to the value given for it.
- # Input variables:
- # Elements is a string containing the list of variable-value pairs.
- # Sep is the string that separates the pairs in the list.
- # AssignOp is the string that separates variables from values.
- # Output variables:
- # Arr is the array.
- # Return value: the number of elements added to the set.
- # Example:
- # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=")
- function Assign(Arr,Elements,Sep,AssignOp,
- Num,Names,Elem,Assignments,Assignment,i) {
- Num = split(Elements,Assignments,Sep)
- for (i = 1; i <= Num; i++) {
- Assignment = Assignments[i]
- Ind = index(Assignment,AssignOp)
- Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1)
- }
- return Num
- }
-
- # Packs Arr[], which should have integer indices starting at or above n, to
- # contiguous integer indices starting with n.
- # If n is not given it defaults to 0.
- # Num should be the number of elements in Arr.
- function PackArr(Arr,Num,n, NewInd,OldInd) {
- NewInd = OldInd = n+0
- for (; Num; Num--) {
- while (!(OldInd in Arr))
- OldInd++
- if (NewInd != OldInd) {
- Arr[NewInd] = Arr[OldInd]
- delete Arr[OldInd]
- }
- OldInd++
- NewInd++
- }
- }
- ### End array routines
- ### Start of ProcArgs library
- # @(#) ProcArgs 1.11 96/12/08
- # 92/02/29 john h. dubois iii (john@armory.com)
- # 93/07/18 Added "#" arg type
- # 93/09/26 Do not count -h against MinArgs
- # 94/01/01 Stop scanning at first non-option arg. Added ">" option type.
- # Removed meaning of "+" or "-" by itself.
- # 94/03/08 Added & option and *()< option types.
- # 94/04/02 Added NoRCopt to Opts()
- # 94/06/11 Mark numeric variables as such.
- # 94/07/08 Opts(): Do not require any args if h option is given.
- # 95/01/22 Record options given more than once. Record option num in argv.
- # 95/06/08 Added ExclusiveOptions().
- # 96/01/20 Let rcfiles be a colon-separated list of filenames.
- # Expand $VARNAME at the start of its filenames.
- # Let varname=0 and -option- turn off an option.
- # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
- # of the vars should be searched for in the environment.
- # Check for duplicate rcfiles.
- # 96/05/13 Return more specific error values. Note: ProcArgs() and InitOpts()
- # now return various negatives values on error, not just -1, and
- # Opts() may set Err to various positive values, not just 1.
- # Added AllowUnrecOpt.
- # 96/05/23 Check type given for & option
- # 96/06/15 Re-port to awk
- # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
- # used by other functions.
- # 96/10/15 Added OptChars
- # 96/11/01 Added exOpts arg to Opts()
- # 96/11/16 Added ; type
- # 96/12/08 Added Opt2Set() & Opt2Sets()
- # 96/12/27 Added CmdLineOpt()
-
- # optlist is a string which contains all of the possible command line options.
- # A character followed by certain characters indicates that the option takes
- # an argument, with type as follows:
- # : String argument
- # ; Non-empty string argument
- # * Floating point argument
- # ( Non-negative floating point argument
- # ) Positive floating point argument
- # # Integer argument
- # < Non-negative integer argument
- # > Positive integer argument
- # The only difference the type of argument makes is in the runtime argument
- # error checking that is done.
-
- # The & option is a special case used to get numeric options without the
- # user having to give an option character. It is shorthand for [-+.0-9].
- # If & is included in optlist and an option string that begins with one of
- # these characters is seen, the value given to "&" will include the first
- # char of the option. & must be followed by a type character other than ":"
- # or ";".
- # Note that if e.g. &> is given, an option of -.5 will produce an error.
-
- # Strings in argv[] which begin with "-" or "+" are taken to be
- # strings of options, except that a string which consists solely of "-"
- # or "+" is taken to be a non-option string; like other non-option strings,
- # it stops the scanning of argv and is left in argv[].
- # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
- # If an option takes an argument, the argument may either immediately
- # follow it or be given separately.
- # "-" and "+" options are treated the same. "+" is allowed because most awks
- # take any -options to be arguments to themselves. gawk 2.15 was enhanced to
- # stop scanning when it encounters an unrecognized option, though until 2.15.5
- # this feature had a flaw that caused problems in some cases. See the OptChars
- # parameter to explicitly set the option-specifier characters.
-
- # If an option that does not take an argument is given,
- # an index with its name is created in Options and its value is set to the
- # number of times it occurs in argv[].
-
- # If an option that does take an argument is given, an index with its name is
- # created in Options and its value is set to the value of the argument given
- # for it, and Options[option-name,"count"] is (initially) set to the 1.
- # If an option that takes an argument is given more than once,
- # Options[option-name,"count"] is incremented, and the value is assigned to
- # the index (option-name,instance) where instance is 2 for the second occurance
- # of the option, etc.
- # In other words, the first time an option with a value is encountered, the
- # value is assigned to an index consisting only of its name; for any further
- # occurances of the option, the value index has an extra (count) dimension.
-
- # The sequence number for each option found in argv[] is stored in
- # Options[option-name,"num",instance], where instance is 1 for the first
- # occurance of the option, etc. The sequence number starts at 1 and is
- # incremented for each option, both those that have a value and those that
- # do not. Options set from a config file have a value of 0 assigned to this.
-
- # Options and their arguments are deleted from argv.
- # Note that this means that there may be gaps left in the indices of argv[].
- # If compress is nonzero, argv[] is packed by moving its elements so that
- # they have contiguous integer indices starting with 0.
- # Option processing will stop with the first unrecognized option, just as
- # though -- was given except that unlike -- the unrecognized option will not be
- # removed from ARGV[]. Normally, an error value is returned in this case.
- # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
- # be found, so the number of remaining arguments is returned instead.
- # If OptChars is not a null string, it is the set of characters that indicate
- # that an argument is an option string if the string begins with one of the
- # characters. A string consisting solely of two of the same option-indicator
- # characters stops the scanning of argv[]. The default is "-+".
- # argv[0] is not examined.
- # The number of arguments left in argc is returned.
- # If an error occurs, the global string OptErr is set to an error message
- # and a negative value is returned.
- # Current error values:
- # -1: option that required an argument did not get it.
- # -2: argument of incorrect type supplied for an option.
- # -3: unrecognized (invalid) option.
- function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
- ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
- NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
- {
- # ArgNum is the index of the argument being processed.
- # ArgsLeft is the number of arguments left in argv.
- # Arg is the argument being processed.
- # ArgLen is the length of the argument being processed.
- # ArgInd is the position of the character in Arg being processed.
- # Option is the character in Arg being processed.
- # Pos is the position in OptList of the option being processed.
- # NumOpt is true if a numeric option may be given.
- ArgsLeft = argc
- NumOpt = index(OptList,"&")
- OptionNum = 0
- if (OptChars == "")
- OptChars = "-+"
- while (OptChars != "") {
- c = substr(OptChars,1,1)
- OptChars = substr(OptChars,2)
- OptCharSet[c]
- OptTerm[c c]
- }
- for (ArgNum = 1; ArgNum < argc; ArgNum++) {
- Arg = argv[ArgNum]
- if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
- break # Not an option; quit
- if (Arg in OptTerm) {
- delete argv[ArgNum]
- ArgsLeft--
- break
- }
- ArgLen = length(Arg)
- for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
- Option = substr(Arg,ArgInd,1)
- if (NumOpt && Option ~ /[-+.0-9]/) {
- # If this option is a numeric option, make its flag be & and
- # its option string flag position be the position of & in
- # the option string.
- Option = "&"
- Pos = NumOpt
- # Prefix Arg with a char so that ArgInd will point to the
- # first char of the numeric option.
- Arg = "&" Arg
- ArgLen++
- }
- # Find position of flag in option string, to get its type (if any).
- # Disallow & as literal flag.
- else if (!(Pos = index(OptList,Option)) || Option == "&") {
- if (AllowUnrecOpt) {
- Escape = 1
- break
- }
- else {
- OptErr = "Invalid option: " specGiven Option
- return -3
- }
- }
-
- # Find what the value of the option will be if it takes one.
- # NeedNextOpt is true if the option specifier is the last char of
- # this arg, which means that if the option requires a value it is
- # the next arg.
- if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
- if (GotValue = ArgNum + 1 < argc)
- Value = argv[ArgNum+1]
- }
- else { # Value is included with option
- Value = substr(Arg,ArgInd + 1)
- GotValue = 1
- }
-
- if (HadValue = AssignVal(Option,Value,Options,
- substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
- specGiven)) {
- if (HadValue < 0) # error occured
- return HadValue
- if (HadValue == 2)
- ArgInd++ # Account for the single-char value we used.
- else {
- if (NeedNextOpt) { # option took next arg as value
- delete argv[++ArgNum]
- ArgsLeft--
- }
- break # This option has been used up
- }
- }
- }
- if (Escape)
- break
- # Do not delete arg until after processing of it, so that if it is not
- # recognized it can be left in ARGV[].
- delete argv[ArgNum]
- ArgsLeft--
- }
- if (compress != 0) {
- dest = 1
- src = argc - ArgsLeft + 1
- for (count = ArgsLeft - 1; count; count--) {
- ARGV[dest] = ARGV[src]
- dest++
- src++
- }
- }
- return ArgsLeft
- }
-
- # Assignment to values in Options[] occurs only in this function.
- # Option: Option specifier character.
- # Value: Value to be assigned to option, if it takes a value.
- # Options[]: Options array to return values in.
- # ArgType: Argument type specifier character.
- # GotValue: Whether any value is available to be assigned to this option.
- # Name: Name of option being processed.
- # OptionNum: Number of this option (starting with 1) if set in argv[],
- # or 0 if it was given in a config file or in the environment.
- # SingleOpt: true if the value (if any) that is available for this option was
- # given as part of the same command line arg as the option. Used only for
- # options from the command line.
- # specGiven is the option specifier character use, if any (e.g. - or +),
- # for use in error messages.
- # Global variables: OptErr
- # Return value: negative value on error, 0 if option did not require an
- # argument, 1 if it did & used the whole arg, 2 if it required just one char of
- # the arg.
- # Current error values:
- # -1: Option that required an argument did not get it.
- # -2: Value of incorrect type supplied for option.
- # -3: Bad type given for option &
- function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
- SingleOpt,specGiven, UsedValue,Err,NumTypes) {
- # If option takes a value... [
- NumTypes = "*()#<>]"
- if (Option == "&" && ArgType !~ "[" NumTypes) { # ]
- OptErr = "Bad type given for & option"
- return -3
- }
-
- if (UsedValue = (ArgType ~ "[:;" NumTypes)) { # ]
- if (!GotValue) {
- if (Name != "")
- OptErr = "Variable requires a value -- " Name
- else
- OptErr = "option requires an argument -- " Option
- return -1
- }
- if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
- OptErr = Err
- return -2
- }
- # Mark this as a numeric variable; will be propogated to Options[] val.
- if (ArgType != ":" && ArgType != ";")
- Value += 0
- if ((Instance = ++Options[Option,"count"]) > 1)
- Options[Option,Instance] = Value
- else
- Options[Option] = Value
- }
- # If this is an environ or rcfile assignment & it was given a value...
- else if (!OptionNum && Value != "") {
- UsedValue = 1
- # If the value is "0" or "-" and this is the first instance of it,
- # do not set Options[Option]; this allows an assignment in an rcfile to
- # turn off an option (for the simple "Option in Options" test) in such
- # a way that it cannot be turned on in a later file.
- if (!(Option in Options) && (Value == "0" || Value == "-"))
- Instance = 1
- else
- Instance = ++Options[Option]
- # Save the value even though this is a flag
- Options[Option,Instance] = Value
- }
- # If this is a command line flag and has a - following it in the same arg,
- # it is being turned off.
- else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
- UsedValue = 2
- if (Option in Options)
- Instance = ++Options[Option]
- else
- Instance = 1
- Options[Option,Instance]
- }
- # If this is a flag assignment without a value, increment the count for the
- # flag unless it was turned off. The indicator for a flag being turned off
- # is that the flag index has not been set in Options[] but it has an
- # instance count.
- else if (Option in Options || !((Option,1) in Options))
- # Increment number of times this flag seen; will inc null value to 1
- Instance = ++Options[Option]
- Options[Option,"num",Instance] = OptionNum
- return UsedValue
- }
-
- # Option is the option letter
- # Value is the value being assigned
- # Name is the var name of the option, if any
- # ArgType is one of:
- # : String argument
- # ; Non-null string argument
- # * Floating point argument
- # ( Non-negative floating point argument
- # ) Positive floating point argument
- # # Integer argument
- # < Non-negative integer argument
- # > Positive integer argument
- # specGiven is the option specifier character use, if any (e.g. - or +),
- # for use in error messages.
- # Returns null on success, err string on error
- function CheckType(ArgType,Value,Option,Name,specGiven, Err,ErrStr) {
- if (ArgType == ":")
- return ""
- if (ArgType == ";") {
- if (Value == "")
- Err = "must be a non-empty string"
- }
- # A number begins with optional + or -, and is followed by a string of
- # digits or a decimal with digits before it, after it, or both
- else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
- Err = "must be a number"
- else if (ArgType ~ "[#<>]" && Value ~ /\./)
- Err = "may not include a fraction"
- else if (ArgType ~ "[()<>]" && Value < 0)
- Err = "may not be negative"
- # (
- else if (ArgType ~ "[)>]" && Value == 0)
- Err = "must be a positive number"
- if (Err != "") {
- ErrStr = "Bad value \"" Value "\". Value assigned to "
- if (Name != "")
- return ErrStr "variable " substr(Name,1,1) " " Err
- else {
- if (Option == "&")
- Option = Value
- return ErrStr "option " specGiven substr(Option,1,1) " " Err
- }
- }
- else
- return ""
- }
-
- # Note: only the above functions are needed by ProcArgs.
- # The rest of these functions call ProcArgs() and also do other
- # option-processing stuff.
-
- # Opts: Process command line arguments.
- # Opts processes command line arguments using ProcArgs()
- # and checks for errors. If an error occurs, a message is printed
- # and the program is exited.
- #
- # Input variables:
- # Name is the name of the program, for error messages.
- # Usage is a usage message, for error messages.
- # OptList the option description string, as used by ProcArgs().
- # MinArgs is the minimum number of non-option arguments that this
- # program should have, non including ARGV[0] and +h.
- # If the program does not require any non-option arguments,
- # MinArgs should be omitted or given as 0.
- # rcFiles, if given, is a colon-seprated list of filenames to read for
- # variable initialization. If a filename begins with ~/, the ~ is replaced
- # by the value of the environment variable HOME. If a filename begins with
- # $, the part from the character after the $ up until (but not including)
- # the first character not in [a-zA-Z0-9_] will be searched for in the
- # environment; if found its value will be substituted, if not the filename will
- # be discarded.
- # rcfiles are read in the order given.
- # Values given in them will not override values given on the command line,
- # and values given in later files will not override those set in earlier
- # files, because AssignVal() will store each with a different instance index.
- # The first instance of each variable, either on the command line or in an
- # rcfile, will be stored with no instance index, and this is the value
- # normally used by programs that call this function.
- # VarNames is a comma-separated list of variable names to map to options,
- # in the same order as the options are given in OptList.
- # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
- # searched for in the environment. If set to -1, all values will be searched
- # for in the environment. Values given in the environment will override
- # those given in the rcfiles but not those given on the command line.
- # NoRCopt, if given, is an additional letter option that if given on the
- # command line prevents the rcfiles from being read.
- # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
- # ExclusiveOptions() for a description of exOpts.
- # Special options:
- # If x is made an option and is given, some debugging info is output.
- # h is assumed to be the help option.
-
- # Global variables:
- # The command line arguments are taken from ARGV[].
- # The arguments that are option specifiers and values are removed from
- # ARGV[], leaving only ARGV[0] and the non-option arguments.
- # The number of elements in ARGV[] should be in ARGC.
- # After processing, ARGC is set to the number of elements left in ARGV[].
- # The option values are put in Options[].
- # On error, Err is set to a positive integer value so it can be checked for in
- # an END block.
- # Return value: The number of elements left in ARGV is returned.
- # Must keep OptErr global since it may be set by InitOpts().
- function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
- AllowUnrecOpt,optChars,exOpts, ArgsLeft,e) {
- if (MinArgs == "")
- MinArgs = 0
- ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
- optChars)
- if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
- if (ArgsLeft >= 0) {
- OptErr = "Not enough arguments"
- Err = 4
- }
- else
- Err = -ArgsLeft
- printf "%s: %s.\nUse -h for help.\n%s\n",
- Name,OptErr,Usage > "/dev/stderr"
- exit 1
- }
- if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
- (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
- {
- print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
- Err = -e
- exit 1
- }
- if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
- {
- printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
- Err = 1
- exit 1
- }
- return ArgsLeft
- }
-
- # ReadConfFile(): Read a file containing var/value assignments, in the form
- # <variable-name><assignment-char><value>.
- # Whitespace (spaces and tabs) around a variable (leading whitespace on the
- # line and whitespace between the variable name and the assignment character)
- # is stripped. Lines that do not contain an assignment operator or which
- # contain a null variable name are ignored, other than possibly being noted in
- # the return value. If more than one assignment is made to a variable, the
- # first assignment is used.
- # Input variables:
- # File is the file to read.
- # Comment is the line-comment character. If it is found as the first non-
- # whitespace character on a line, the line is ignored.
- # Assign is the assignment string. The first instance of Assign on a line
- # separates the variable name from its value.
- # If StripWhite is true, whitespace around the value (whitespace between the
- # assignment char and trailing whitespace on the line) is stripped.
- # VarPat is a pattern that variable names must match.
- # Example: "^[a-zA-Z][a-zA-Z0-9]+$"
- # If FlagsOK is true, variables are allowed to be "set" by being put alone on
- # a line; no assignment operator is needed. These variables are set in
- # the output array with a null value. Lines containing nothing but
- # whitespace are still ignored.
- # Output variables:
- # Values[] contains the assignments, with the indexes being the variable names
- # and the values being the assigned values.
- # Lines[] contains the line number that each variable occured on. A flag set
- # is record by giving it an index in Lines[] but not in Values[].
- # Return value:
- # If any errors occur, a string consisting of descriptions of the errors
- # separated by newlines is returned. In no case will the string start with a
- # numeric value. If no errors occur, the number of lines read is returned.
- function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
- FlagsOK,
- Line,Status,Errs,AssignLen,LineNum,Var,Val) {
- if (Comment != "")
- Comment = "^" Comment
- AssignLen = length(Assign)
- if (VarPat == "")
- VarPat = "." # null varname not allowed
- while ((Status = (getline Line < File)) == 1) {
- LineNum++
- sub("^[ \t]+","",Line)
- if (Line == "") # blank line
- continue
- if (Comment != "" && Line ~ Comment)
- continue
- if (Pos = index(Line,Assign)) {
- Var = substr(Line,1,Pos-1)
- Val = substr(Line,Pos+AssignLen)
- if (StripWhite) {
- sub("^[ \t]+","",Val)
- sub("[ \t]+$","",Val)
- }
- }
- else {
- Var = Line # If no value, var is entire line
- Val = ""
- }
- if (!FlagsOK && Val == "") {
- Errs = Errs \
- sprintf("\nBad assignment on line %d of file %s: %s",
- LineNum,File,Line)
- continue
- }
- sub("[ \t]+$","",Var)
- if (Var !~ VarPat) {
- Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
- LineNum,File,Var)
- continue
- }
- if (!(Var in Lines)) {
- Lines[Var] = LineNum
- if (Pos)
- Values[Var] = Val
- }
- }
- if (Status)
- Errs = Errs "\nCould not read file " File
- close(File)
- return Errs == "" ? LineNum : substr(Errs,2) # Skip first newline
- }
-
- # Variables:
- # Data is stored in Options[].
- # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
- # Global vars:
- # Sets OptErr. Uses ENVIRON[].
- # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
- function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
- Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
- fNames,numrcFiles,filesRead,Err,Values,retStr) {
- split("",filesRead,"") # make awk know this is an array
- NumVars = split(VarNames,Vars,",")
- TypesInd = Ret = 0
- if (EnvSearch == -1)
- EnvSearch = NumVars
- for (i = 1; i <= NumVars; i++) {
- Var = Vars[i]
- CharOpt = substr(OptList,++TypesInd,1)
- if (CharOpt ~ "^[:;*()#<>&]$")
- CharOpt = substr(OptList,++TypesInd,1)
- Map[Var] = CharOpt
- Types[Var] = Type = substr(OptList,TypesInd+1,1)
- # Do not overwrite entries from environment
- if (i <= EnvSearch && Var in ENVIRON &&
- (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
- return Err
- }
-
- numrcFiles = split(rcFiles,fNames,":")
- for (i = 1; i <= numrcFiles; i++) {
- rcFile = fNames[i]
- if (rcFile ~ "^~/")
- rcFile = ENVIRON["HOME"] substr(rcFile,2)
- else if (rcFile ~ /^\$/) {
- rcFile = substr(rcFile,2)
- match(rcFile,"^[a-zA-Z0-9_]*")
- envvar = substr(rcFile,1,RLENGTH)
- if (envvar in ENVIRON)
- rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
- else
- continue
- }
- if (rcFile in filesRead)
- continue
- # rcfiles are liable to be given more than once, e.g. UHOME and HOME
- # may be the same
- filesRead[rcFile]
- if ("x" in Options)
- printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
- retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
- if (retStr > 0)
- READ_RCFILE = 1
- else if (ret != "") {
- OptErr = retStr
- Ret = -1
- }
- for (Var in Lines)
- if (Var in Map) {
- if ((Err = AssignVal(Map[Var],
- Var in Values ? Values[Var] : "",Options,Types[Var],
- Var in Values,Var,0)) < 0)
- return Err
- }
- else {
- OptErr = sprintf(\
- "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
- Lines[Var],rcFile)
- Ret = -1
- }
- }
-
- if ("x" in Options)
- for (Var in Map)
- if (Map[Var] in Options)
- printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
- "/dev/stderr"
- else
- printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
- return Ret
- }
-
- # OptSets is a semicolon-separated list of sets of option sets.
- # Within a list of option sets, the option sets are separated by commas. For
- # each set of sets, if any option in one of the sets is in Options[] AND any
- # option in one of the other sets is in Options[], an error string is returned.
- # If no conflicts are found, nothing is returned.
- # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
- # the exclusions presented by the first set of sets (ab,def,g) if:
- # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
- # (a or b is in Options[]) AND (g is in Options) OR
- # (d, e, or f is in Options[]) AND (g is in Options)
- # An error will be returned due to the exclusions presented by the second set
- # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
- # todo: make options given on command line unset options given in config file
- # todo: that they conflict with.
- function ExclusiveOptions(OptSets,Options,
- Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
- SetNum,OSetNum) {
- NumSetSets = split(OptSets,SetSets,";")
- # For each set of sets...
- for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
- # NumSets is the number of sets in this set of sets.
- NumSets = split(SetSets[SetSet],Sets,",")
- # For each set in a set of sets except the last...
- for (SetNum = 1; SetNum < NumSets; SetNum++) {
- s1 = Sets[SetNum]
- L1 = length(s1)
- for (Pos1 = 1; Pos1 <= L1; Pos1++)
- # If any of the options in this set was given, check whether
- # any of the options in the other sets was given. Only check
- # later sets since earlier sets will have already been checked
- # against this set.
- if ((c1 = substr(s1,Pos1,1)) in Options)
- for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
- s2 = Sets[OSetNum]
- L2 = length(s2)
- for (Pos2 = 1; Pos2 <= L2; Pos2++)
- if ((c2 = substr(s2,Pos2,1)) in Options)
- ErrStr = ErrStr "\n"\
- sprintf("Cannot give both %s and %s options.",
- c1,c2)
- }
- }
- }
- if (ErrStr != "")
- return substr(ErrStr,2)
- return ""
- }
-
- # The value of each instance of option Opt that occurs in Options[] is made an
- # index of Set[].
- # The return value is the number of instances of Opt in Options.
- function Opt2Set(Options,Opt,Set, count) {
- if (!(Opt in Options))
- return 0
- Set[Options[Opt]]
- count = Options[Opt,"count"]
- for (; count > 1; count--)
- Set[Options[Opt,count]]
- return count
- }
-
- # The value of each instance of option Opt that occurs in Options[] that
- # begins with "!" is made an index of nSet[] (with the ! stripped from it).
- # Other values are made indexes of Set[].
- # The return value is the number of instances of Opt in Options.
- function Opt2Sets(Options,Opt,Set,nSet, count,aSet,ret) {
- ret = Opt2Set(Options,Opt,aSet)
- for (value in aSet)
- if (substr(value,1,1) == "!")
- nSet[substr(value,2)]
- else
- Set[value]
- return ret
- }
-
- # Returns true if option Opt was given on the command line.
- function CmdLineOpt(Options,Opt, i) {
- for (i = 1; (Opt,"num",i) in Options; i++)
- if (Options[Opt,"num",i] != 0)
- return 1
- return 0
- }
- ### End of ProcArgs library
- # @(#) CmdReadLine 95/09/04
- # Run Command, read a single line of output from it, then close it.
- # If Verbose is true, a complaint is issued if the read fails.
- # Output is returned in $*
- # The return value from getline is returned. It will be 1 on a successful
- # read; 0 if no lines were read due because the command produced no output
- # or could not be run. ERRNO is never set since pipes are run by a shell.
- function CmdReadLine(Command,Verbose, ret) {
- if (Debug) {
- print "* Issuing command: " Command "\n"\
- "* Waiting for single line of output..." > "/dev/stderr"
- }
- ret = Command | getline
- if (Verbose && ret != 1)
- printf "Read from pipe \"%s\" failed\n",Command
- # close does not return a value under awk, only gawk
- close(Command)
- if (Debug)
- print "* Output: " $0 > "/dev/stderr"
- return ret
- }
- ### Begin qsort routines
-
- # Arr[] is an array of values with arbitrary indices.
- # k[] is returned with numeric indices 1..n.
- # The values in k[] are the indices of Arr[],
- # ordered so that if Arr[] is stepped through
- # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
- # through in order of the values of its elements.
- # The return value is the number of elements in the arrays (n).
- function qsortArbIndByValue(Arr,k, ArrInd,ElNum) {
- ElNum = 0
- for (ArrInd in Arr)
- k[++ElNum] = ArrInd
- qsortSegment(Arr,k,1,ElNum)
- return ElNum
- }
-
- # Sort a segment of an array.
- # Arr[] contains data with arbitrary indices.
- # k[] has indices 1..nelem, with the indices of arr[] as values.
- # This function sorts the elements of arr that are pointed to by
- # k[start..end], swapping the values of elements of k[] so that
- # when this function returns arr[k[start..end]] will be in order.
- function qsortSegment(Arr,k,start,end, left,right,sepval,tmp,tmpe,tmps) {
- # handle two-element case explicitly for a tiny speedup
- if ((end - start) == 1) {
- if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
- k[start] = tmpe
- k[end] = tmps
- }
- return
- }
- # Make sure comparisons act on these as numbers
- left = start+0
- right = end+0
- sepval = Arr[k[int((left + right) / 2)]]
- # Make every element <= sepval be to the left of every element > sepval
- while (left < right) {
- while (Arr[k[left]] < sepval)
- left++
- while (Arr[k[right]] > sepval)
- right--
- if (left < right) {
- tmp = k[left]
- k[left++] = k[right]
- k[right--] = tmp
- }
- }
- if (left == right)
- if (Arr[k[left]] < sepval)
- left++
- else
- right--
- if (start < right)
- qsortSegment(Arr,k,start,right)
- if (left < end)
- qsortSegment(Arr,k,left,end)
- }
-
- # Arr[] is an array of values with arbitrary indices.
- # k[] is returned with numeric indices 1..n.
- # The values in k are the indices of Arr[],
- # ordered so that if Arr[] is stepped through
- # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
- # through in order of the values of its indices.
- # The return value is the number of elements in the arrays (n).
- # If the indexes are numeric, Numeric should be true, so that they can be
- # compared as such rather than as strings. Numeric indexes do not have to be
- # contiguous.
- function qsortByArbIndex(Arr,k,Numeric, ArrInd,ElNum) {
- ElNum = 0
- if (Numeric)
- # Indexes do not preserve numeric type, so must be forced
- for (ArrInd in Arr)
- k[++ElNum] = ArrInd+0
- else
- for (ArrInd in Arr)
- k[++ElNum] = ArrInd
- qsortNumIndByValue(k,1,ElNum)
- return ElNum
- }
-
- # Arr is an array of elements with contiguous numeric indexes to be sorted
- # by value.
- # start and end are the starting and ending indexes of the range to be sorted.
- function qsortNumIndByValue(Arr,start,end, left,right,sepval,tmp,tmpe,tmps) {
- # handle two-element case explicitly for a tiny speedup
- if ((start - end) == 1) {
- if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
- Arr[start] = tmpe
- Arr[end] = tmps
- }
- return
- }
- left = start+0
- right = end+0
- sepval = Arr[int((left + right) / 2)]
- while (left < right) {
- while (Arr[left] < sepval)
- left++
- while (Arr[right] > sepval)
- right--
- if (left <= right) {
- tmp = Arr[left]
- Arr[left++] = Arr[right]
- Arr[right--] = tmp
- }
- }
- if (start < right)
- qsortNumIndByValue(Arr,start,right)
- if (left < end)
- qsortNumIndByValue(Arr,left,end)
- }
-
- ### End qsort routines
- ### Begin pwent library
-
- # @(#) pwent.awk 1.3 96/12/15
- # 92/08/10 john h. dubois III (john@armory.com)
- # 93/12/13 fixed to not clobber $*
- # 96/01/05 Send error messages to /dev/stderr
- # 96/05/24 Let getpwnam() return a specific field if requested.
- # Added PW_REAL and PW_OFFICE.
- # 96/06/03 Added Type field to getpwent()
- # 96/06/24 Allow a Field to be requested for getpwent() also.
- # 96/06/29 Added PW_RECORD, and getpwreal().
- # Changed PWLines to be index by record number instead of name.
- # 96/11/17 Added getpwuid()
- # 96/12/15 Added pwFieldNames, and pwent() as general select-by-field routine.
-
- # Require: ReadShells()
-
- # getpwent, getpwnam: get an entry from the passwd file.
- # Each of the following passwd functions returns an array which contains
- # a passwd file entry. The array contains the fields of the entry.
- # Global variables:
- # The following variables are defined with the values of the indexes of the
- # entries: PW_NAME, PW_PASSWORD, PW_UID, PW_GID, PW_GCOS, PW_HOME, PW_SHELL
- # PWLines[] contains the lines of the password file, indexed by record number,
- # starting with 1.
- # _pwNames[] is a mapping of name to passwd record number.
- # pwFieldNames[field-index] is set to a short description of each field.
- # getpwentNum is the number of the next entry to be returned by getpwent().
-
- # Left FS global because making it local does not work in gawk.
- function ReadPasswd( User,Line,i,Ind,ret) {
- if (PW_Name)
- return 1
- PW_NAME = 1
- PW_PASSWORD = 2
- PW_UID = 3
- PW_GID = 4
- PW_GCOS = 5
- PW_HOME = 6
- PW_SHELL = 7
- PW_REAL = -1 # for PWGetFields()
- PW_OFFICE = -2
- PW_RECORD = -3
-
- split(\
- "user name,password,UID,login GID,GCOS,home directory,login shell",
- pwFieldNames,",")
- pwFieldNames[-1] = "real name"
- pwFieldNames[-2] = "office"
- pwFieldNames[-3] = "record number"
- getpwentNum = 1
- while ((ret = (getline Line < "/etc/passwd")) == 1) {
- PWLines[++Ind] = Line
- _pwNames[substr(Line,1,index(Line,":")-1)] = Ind
- }
- _num_pw = Ind
- close("/etc/passwd")
- if (ret) {
- printf "ReadPasswd(): Could not open /etc/passwd: %s\n",
- ERRNO > "/dev/stderr"
- return 0
- }
- return 1
- }
-
- # setpwent resets the passwd file entry pointer used by getpwent
- # to the first entry.
- function setpwent() {
- getpwentNum = 1
- }
-
- # getpwent sets PWEnt to the next entry in the passwd file.
- # If Type is set to -1, the entry for the next "real" user is returned (others
- # are skipped over), where a real user is a user whose login shell is listed in
- # /etc/shells. This requires the ReadShells() function. Other values for
- # Type are not yet defined and are ignored.
- # If the last entry has already been returned, 0 is return if Field is null,
- # ":" if not.
- # If the entry for the next real user has been requested and /etc/shells
- # cannot be read, -1 is returned if Field is null, "\n" if not.
- # See PWGetFields() for other return values and the meaning of the Field
- # parameter.
- function getpwent(PWEnt,Type,Field, entNum) {
- if (!PW_NAME)
- ReadPasswd()
- if (!(getpwentNum in PWLines))
- return Field ? ":" : 0
- if (Type == -1) {
- if (!_DidReadShells && ReadShells(LoginShells) == -1)
- return Field ? "\n" : -1
- split(PWLines[getpwentNum++],PWEnt,":")
- while (!(PWEnt[PW_SHELL] in LoginShells)) {
- if (!(getpwentNum in PWLines))
- return Field ? ":" : 0
- split(PWLines[getpwentNum++],PWEnt,":")
- }
- return PWGetFields("",PWEnt,Field,getpwentNum - 1)
- }
- else {
- entNum = getpwentNum
- return PWGetFields(PWLines[getpwentNum++],PWEnt,Field,entNum)
- }
- }
-
- # PWGetFields() splits PWLine into PWEnt[], and optionally returns a field
- # from it. If PWLine is null, PWEnt[] is assumed to have already been filled
- # in with a password entry.
- # If Field is not passed or is null, the return value is 1.
- # If Field is non-null, it should a PW_ value. In this case, the value of the
- # requested field is returned.
- # If entNum is nonzero, it is the value that PWEnt[PW_RECORD] should be set to.
- # It should be the index in PWLines[] of the record being processed.
- # In addition to the PW_ values used by the rest of the functions in this
- # library, this function can be passed PW_REAL and PW_OFFICE.
- # PW_REAL will get the part of the GCOS field before the first comma.
- # PW_OFFICE will get the part of the GCOS field after the first comma.
- # If either of these is requested, both values will also be assigned to their
- # indices in PWEnt[], unless there is no comma in the GCOS field, in which case
- # PW_OFFICE will not be set.
- # NOTE: since the global field names are set in ReadShells(), it must be
- # executed before any of the field name can be passed.
- function PWGetFields(PWLine,PWEnt,Field,entNum, gcos,ind) {
- if (PWLine != "")
- split(PWLine,PWEnt,":")
- if (entNum)
- PWEnt[PW_RECORD] = entNum
- if (!Field)
- return 1
- if (Field < 0) {
- if (ind = index(gcos = PWEnt[PW_GCOS],",")) {
- PWEnt[PW_OFFICE] = substr(gcos,ind+1)
- PWEnt[PW_REAL] = substr(gcos,1,ind-1)
- }
- else
- PWEnt[PW_REAL] = gcos
- }
- return PWEnt[Field]
- }
-
- # getpwnam sets PWEnt to the passwd entry for login name Name.
- # If Next is true or Name does not exist in the password file, the return value
- # is ":" if Field was passed, 0 if not.
- # For other return values and parameter explanation, see PWGetFields()
- function getpwnam(Name,PWEnt,Field,Next) {
- if (!PW_NAME)
- ReadPasswd()
- if (!Next && Name in _pwNames)
- return PWGetFields(PWLines[_pwNames[Name]],PWEnt,Field,_pwNames[Name])
- else
- return Field ? ":" : 0
- }
-
- # Build uid->pw-index and home->pw-index
- # Globals: _pwIndexes[]
- function MakeInd( Elem,Ind,Line,i,Fields,f) {
- Fields[PW_UID]
- Fields[PW_HOME]
- Fields[PW_GID]
- Fields[PW_SHELL]
- for (Ind = 1; Ind in PWLines; Ind++) {
- Line = PWLines[Ind]
- split(Line,Elem,":")
- for (f in Fields) {
- i = f ":" Elem[f]
- if (i in _pwIndexes)
- _pwIndexes[i] = _pwIndexes[i] "," Ind
- else
- _pwIndexes[i] = Ind
- }
- }
- IndDone = 1
- }
-
- # getpw sets PWEnt to the first passwd entry whose FieldNum'th field is equal
- # to Value. FieldNum may select any field except the password field.
- # If Next is true, Value is ignored and the next entry in the password file
- # that has the same FieldNum field as the last one requested is returned.
- # See getpwnam() for return values and the meaning of the Field param.
- # IgnoreCase and Full are used only for PW_GCOS searches; see getpwreal()
- # for their meaning.
- # Uses globals _pwMatches[] to track matches between calls.
- function getpw(FieldNum,Value,PWEnt,Field,Next,IgnoreCase,Full, elem,i,n) {
- if (FieldNum == PW_GCOS)
- return getpwreal(Value,PWEnt,Field,Next,IgnoreCase,Full)
- if (FieldNum == PW_NAME)
- return getpwnam(Value,PWEnt,Field,Next)
- if (Next) {
- if ((FieldNum,_pwMatches[FieldNum,"cur"]) in _pwMatches) {
- i = _pwMatches[FieldNum,_pwMatches[FieldNum,"cur"]++]
- return PWGetFields(PWLines[i],PWEnt,Field,i)
- }
- }
- else {
- if (!IndDone)
- MakeInd()
- Value = FieldNum ":" Value
- if (Value in _pwIndexes) {
- n = split(_pwIndexes[Value],elem,",")
- for (i = 1; i <= n; i++)
- _pwMatches[FieldNum,i] = elem[i]
- delete _pwMatches[FieldNum,n+1] # so we know when to end
- _pwMatches[FieldNum,"cur"] = 2
- return PWGetFields(PWLines[elem[1]],PWEnt,Field,elem[1])
- }
- }
- return Field ? ":" : 0
- }
-
- function getpwhome(home,PWEnt,Field,Next) {
- if (!PW_NAME) # set PW_ values
- ReadPasswd()
- return getpw(PW_HOME,home,PWEnt,Field,Next)
- }
-
- function getpwuid(UID,PWEnt,Field,Next) {
- if (!PW_NAME) # set PW_ values
- ReadPasswd()
- return getpw(PW_UID,(UID+0),PWEnt,Field,Next)
- }
-
- # Make an index by real name. For each passwd file entry, the real-name
- # is lowercased and split into components on non-alphanums. The passwd entry
- # index that the name came from is added to the value of each such component
- # in the global _RealInd[]. The indexes stored this way are separated by
- # commas. If the real-name contains no alphanums, its index is stored under
- # the null index.
- function _makeRealInd( PWEnt,ret,Elem,nelem,i,Component) {
- setpwent()
- while ((ret = getpwent(PWEnt,"",PW_REAL)) != ":") {
- nelem = split(tolower(ret),Elem,/[^a-z0-9]+/)
- for (i = 1; i <= nelem; i++) {
- Component = Elem[i]
- if (Component == "" && nelem > 1)
- continue
- if (Component in _RealInd)
- _RealInd[Component] = _RealInd[Component] "," PWEnt[PW_RECORD]
- else
- _RealInd[Component] = PWEnt[PW_RECORD]
- }
- }
- _realIndDone = 1
- }
-
- # Make Name into a pattern that will match a name that contains all of the
- # same name components (sequences of alphanums) in the same order. If Name
- # contains no name components, a null string is returned.
- function MakeNamePat(Name, Elem,nelem,i,Pat,e) {
- nelem = split(Name,Elem,/[^a-zA-Z0-9]+/)
- for (i = 1; i <= nelem; i++) {
- if ((e = Elem[i]) == "")
- continue
- if (Pat == "")
- Pat = "(^|[^a-zA-Z0-9])" e
- else
- Pat = Pat "[^a-zA-Z0-9](.*[^a-zA-Z0-9])?" e
- }
- if (Pat == "") # If Name contained no alphanums...
- return ""
- Pat = Pat "([^a-zA-Z0-9]|$)"
- return Pat
- }
-
- # getpwgreal sets PWEnt to the first passwd entry whose PW_REAL (see
- # PWGetFields()) field matches Real. Matching occurs if the alphanumeric
- # components of Real occur in the same order in the entry. Non-alphanums are
- # ignored. All of the components in Real must occur in the entry, but not all
- # of the components in the entry must occur in Real.
- # If the given name does not exist in the password file,
- # the return value is ":" if Field was passed, 0 if not.
- # If Next is true, getpwreal() sets PWEnt to the next passwd entry whose
- # PW_REAL field matches the last previous Real parameter passed.
- # In this case, if the last entry has already been returned,
- # the return value is ":" if Field was passed, 0 if not.
- # Different IgnoreCase and Full parameters may be given when doing a Next
- # search. Both must always be passed; they do not default to the original
- # values when doing a Next search. The only parameter ignored when doing a
- # Next search is Real.
- # If IgnoreCase is true, case is ignored when searching.
- # If Full is true, a match of the full name is required (including any
- # punctuation).
- # For successful return values and Field parameter explanation,
- # see PWGetFields()
- # Globals: For the Next search, between invokations these varies store values:
- # _getpwrealInd[]: The set of pw indices that matched the query.
- # _getpwrealIndInd: The next index in _getpwrealInd[] to look at.
- # _getpwrealReal: The Real value passed with the original query.
- # _getpwrealPat: Real converted to a component order search pattern.
- function getpwreal(Real,PWEnt,Field,Next,IgnoreCase,Full, ind,name,Pat) {
- if (!Next) {
- if (!PW_NAME)
- ReadPasswd()
- if (!_realIndDone)
- _makeRealInd()
- _getpwrealReal = Real
- _getpwrealPat = MakeNamePat(Real)
- # Get first component from Real
- Real = tolower(Real)
- gsub("^[^a-z0-9]+","",Real)
- gsub("[^a-z0-9].*","",Real)
- if (!(Real in _RealInd))
- return Field ? ":" : 0
- split(_RealInd[Real],_getpwrealInd,",")
- _getpwrealIndInd = 1
- }
- if (Full)
- Pat = _getpwrealReal
- else
- Pat = _getpwrealPat
- if (IgnoreCase)
- Pat = tolower(Pat)
- while (_getpwrealIndInd in _getpwrealInd) {
- ind = _getpwrealInd[_getpwrealIndInd++]
- name = PWGetFields(PWLines[ind],PWEnt,PW_REAL,ind)
- if (IgnoreCase)
- name = tolower(name)
- if (Full ? (name == Pat) : (name ~ Pat))
- return PWGetFields("",PWEnt,Field,ind)
- }
- return Field ? ":" : 0
- }
-
- ### End pwent library
- ### Begin min,max,In routines
-
- function min(a,b) {
- if (a < b)
- return a
- else
- return b
- }
-
- function max(a,b) {
- if (a > b)
- return a
- else
- return b
- }
-
- function In(Val,Min,Max) {
- return (Min <= Val && Val <= Max)
- }
-
- # Return (in Ind) the indices of the elements with the smallest value in A.
- # The smallest value is returned as the function value.
- # If there are no elements in A, null is returned.
- function arrMin(A,Ind, i,min) {
- for (i in A)
- if (min == "" || A[i] < min) {
- DeleteAll(Ind)
- min = A[i]
- Ind[i]
- }
- else if (A[i] == min)
- Ind[i]
- return min
- }
-
- ### End min,max,In routines
-